﻿using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Text;
using System.Numerics;
using System.Drawing;
using System.Collections.Specialized;

namespace IOP.OpenType.Rasterizer
{
    /// <summary>
    /// 光栅化命令
    /// </summary>
    public static class RasterizationCommand
    {
        /// <summary>
        /// 绘制直线
        /// </summary>
        /// <param name="bitmap"></param>
        /// <param name="x0"></param>
        /// <param name="y0"></param>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        /// <param name="color"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void PlotLine(byte[,] bitmap, int x0, int y0, int x1, int y1, byte color)
        {
            int dx = Math.Abs(x1 - x0);
            int sx = x0 < x1 ? 1 : -1;
            int dy = -Math.Abs(y1 - y0);
            int sy = y0 < y1 ? 1 : -1;
            int err = dx + dy, e2;
            for (; ; )
            {
                bitmap[y0, x0] = color;
                e2 = 2 * err;
                if (e2 >= dy)
                {
                    if (x0 == x1)
                        break;
                    err += dy; x0 += sx;
                }
                if (e2 <= dx)
                {
                    if (y0 == y1)
                        break;
                    err += dx; y0 += sy;
                }
            }
        }

        /// <summary>
        /// 绘制二次贝塞尔曲线
        /// </summary>
        /// <param name="bitmap"></param>
        /// <param name="x0"></param>
        /// <param name="y0"></param>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        /// <param name="x2"></param>
        /// <param name="y2"></param>
        /// <param name="color"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void PlotQuadBezier(byte[,] bitmap, int x0, int y0, int x1, int y1, int x2, int y2, byte color)
        {
            /* plot any quadratic Bezier curve */
            int x = x0 - x1, y = y0 - y1;
            double t = x0 - 2 * x1 + x2, r;
            if ((long)x * (x2 - x1) > 0)
            { /* horizontal cut at P4? */
                if ((long)y * (y2 - y1) > 0) /* vertical cut at P6 too? */
                    if (Math.Abs((y0 - 2 * y1 + y2) / t * x) > Math.Abs(y))
                    { /* which first? */
                        x0 = x2; x2 = x + x1; y0 = y2; y2 = y + y1; /* swap points */
                    } /* now horizontal cut at P4 comes first */
                t = (x0 - x1) / t;
                r = (1 - t) * ((1 - t) * y0 + 2.0 * t * y1) + t * t * y2; /* By(t=P4) */
                t = (x0 * x2 - x1 * x1) * t / (x0 - x1); /* gradient dP4/dx=0 */
                x = (int)Math.Floor(t + 0.5); y = (int)Math.Floor(r + 0.5);
                r = (y1 - y0) * (t - x0) / (x1 - x0) + y0; /* intersect P3 | P0 P1 */
                PlotQuadBezierSeg(bitmap, x0, y0, x, (int)Math.Floor(r + 0.5), x, y, color);
                r = (y1 - y2) * (t - x2) / (x1 - x2) + y2; /* intersect P4 | P1 P2 */
                x0 = x1 = x; y0 = y; y1 = (int)Math.Floor(r + 0.5); /* P0 = P4, P1 = P8 */
            }
            if ((long)(y0 - y1) * (y2 - y1) > 0)
            { /* vertical cut at P6? */
                t = y0 - 2 * y1 + y2; t = (y0 - y1) / t;
                r = (1 - t) * ((1 - t) * x0 + 2.0 * t * x1) + t * t * x2; /* Bx(t=P6) */
                t = (y0 * y2 - y1 * y1) * t / (y0 - y1); /* gradient dP6/dy=0 */
                x = (int)Math.Floor(r + 0.5); y = (int)Math.Floor(t + 0.5);
                r = (x1 - x0) * (t - y0) / (y1 - y0) + x0; /* intersect P6 | P0 P1 */
                PlotQuadBezierSeg(bitmap, x0, y0, (int)Math.Floor(r + 0.5), y, x, y, color);
                r = (x1 - x2) * (t - y2) / (y1 - y2) + x2; /* intersect P7 | P1 P2 */
                x0 = x; x1 = (int)Math.Floor(r + 0.5); y0 = y1 = y; /* P0 = P6, P1 = P7 */
            }
            PlotQuadBezierSeg(bitmap, x0, y0, x1, y1, x2, y2, color);
        }

        /// <summary>
        /// 种子扫描线填充算法
        /// </summary>
        /// <param name="bitmap"></param>
        /// <param name="seed"></param>
        /// <param name="color"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void SeedScanFill(byte[,] bitmap, Vector2 seed, byte color)
        {
            int width = bitmap.GetLength(1);
            int height = bitmap.GetLength(0);
            Stack<Point> stack = new Stack<Point>();
            stack.Push(new Point((int)seed.X, (int)seed.Y));
            while (stack.TryPop(out Point p))
            {
                int x = p.X;
                int y = p.Y;
                while (x < width && bitmap[y, x] != color)
                {
                    bitmap[y, x] = color;
                    x++;
                }
                int xr = x;
                x = p.X - 1;
                while (x >= 0 && bitmap[y, x] != color)
                {
                    bitmap[y, x] = color;
                    x--;
                }
                int xl = x + 1;
                x = xl;
                y++;
                if (y < height)
                {
                    while (x < xr)
                    {
                        bool spanNeedFile = false;
                        while (x < xr && bitmap[y, x] != color)
                        {
                            spanNeedFile = true;
                            x++;
                        }
                        if (spanNeedFile)
                        {
                            stack.Push(new Point(x - 1, y));
                            spanNeedFile = false;
                        }
                        while (x < xr && bitmap[y, x] == color) x++;
                    }
                }
                x = xl;
                y -= 2;
                if (y >= 0)
                {
                    while (x < xr)
                    {
                        bool spanNeedFile = false;
                        while (x < xr && bitmap[y, x] != color)
                        {
                            spanNeedFile = true;
                            x++;
                        }
                        if (spanNeedFile)
                        {
                            stack.Push(new Point(x - 1, y));
                            spanNeedFile = false;
                        }
                        while (x < xr && bitmap[y, x] == color) x++;
                    }
                }
            }
        }

        /// <summary>
        /// 8ssedt算法
        /// </summary>
        /// <param name="bitmap"></param>
        /// <param name="sdfGrid1"></param>
        /// <param name="sdfGrid2"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void EightSsedt(byte[,] bitmap, SDFGrid? sdfGrid1, SDFGrid? sdfGrid2)
        {
            if (bitmap == null) throw new ArgumentNullException(nameof(bitmap));
            int height = bitmap.GetLength(0);
            int width = bitmap.GetLength(1);
            SDFGrid grid1, grid2;
            if (sdfGrid1.HasValue)
            {
                grid1 = sdfGrid1.Value;
                grid1.Clear();
            }
            else grid1 = new SDFGrid { Distance = new Point[height, width] };
            if (sdfGrid2.HasValue)
            {
                grid2 = sdfGrid2.Value;
                grid2.Clear();
            }
            else grid2 = new SDFGrid { Distance = new Point[height, width] };
            for (int y = 0; y < height; y++)
            {
                for(int x = 0; x < width; x++)
                {
                    byte b = bitmap[y, x];
                    if(b < 128)
                    {
                        grid1.SetDistance(x, y, SDFGrid.Inside);
                        grid2.SetDistance(x, y, SDFGrid.Empty);
                    }
                    else
                    {
                        grid1.SetDistance(x, y, SDFGrid.Empty);
                        grid2.SetDistance(x, y, SDFGrid.Inside);
                    }
                }
            }
            GenerateSDF(ref grid1);
            GenerateSDF(ref grid2);
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    double dist1 = Math.Sqrt(SDFGrid.DistSq(grid1.GetDistance(x, y)));
                    double dist2 = Math.Sqrt(SDFGrid.DistSq(grid2.GetDistance(x, y)));
                    int dist = (int)(dist1 - dist2);
                    int c = dist + 255;
                    if (c < 0) c = 0;
                    else if (c > 255) c = 255;
                    bitmap[y, x] = (byte)c;
                }
            }
        }
        /// <summary>
        /// 暴力搜索算法
        /// </summary>
        /// <param name="source">源数据</param>
        /// <param name="output">输出数据</param>
        /// <param name="assist">协助数组</param>
        /// <param name="targetAssist"></param>
        /// <param name="color">深度</param>
        public static void GenerateSDF(byte[,] source, byte[,] output, Pixel[,] assist, Pixel[,] targetAssist, byte color)
        {
            int sourceWidth = source.GetLength(1);
            int sourceHeight = source.GetLength(0);
            int targetWidth = output.GetLength(1);
            int targetHeight = output.GetLength(0);
            int x, y;
            for (y = 0; y < sourceWidth; y++)
            {
                for (x = 0; x < sourceHeight; x++)
                {
                    if (source[y, x] == color) assist[y, x].IsIn = true;
                    else assist[y, x].IsIn = false;
                }
            }

            int gapX = sourceWidth / targetWidth;
            int gapY = sourceHeight / targetHeight;
            int MAX_SEARCH_DIST = 512;
            int minx, maxx, miny, maxy;
            float max_distance = -MAX_SEARCH_DIST;
            float min_distance = MAX_SEARCH_DIST;

            for (x = 0; x < targetWidth; x++)
            {
                for (y = 0; y < targetHeight; y++)
                {
                    int sourceX = x * gapX;
                    int sourceY = y * gapY;
                    int min = MAX_SEARCH_DIST;
                    minx = sourceX - MAX_SEARCH_DIST;
                    if (minx < 0) minx = 0;
                    miny = sourceY - MAX_SEARCH_DIST;
                    if (miny < 0) miny = 0;
                    maxx = sourceX + MAX_SEARCH_DIST;
                    if (maxx > sourceWidth) maxx = sourceWidth;
                    maxy = sourceY + MAX_SEARCH_DIST;
                    if (maxy > sourceHeight) maxy = sourceHeight;
                    int dx, dy, iy, ix, distance;
                    bool sourceIsInside = assist[sourceY, sourceX].IsIn;
                    if (sourceIsInside)
                    {
                        for (iy = miny; iy < maxy; iy++)
                        {
                            dy = iy - sourceY;
                            dy *= dy;
                            for (ix = minx; ix < maxx; ix++)
                            {
                                bool targetIsInside = assist[iy, ix].IsIn;
                                if (targetIsInside) continue;
                                dx = ix - sourceX;
                                distance = (int)MathF.Sqrt(dx * dx + dy);
                                if (distance < min) min = distance;
                            }
                        }
                        if (min > max_distance) max_distance = min;
                        targetAssist[y, x].Distance = min;
                    }
                    else
                    {
                        for (iy = miny; iy < maxy; iy++)
                        {
                            dy = iy - sourceY;
                            dy *= dy;
                            for (ix = minx; ix < maxx; ix++)
                            {
                                bool targetIsInside = assist[iy, ix].IsIn;
                                if (!targetIsInside) continue;
                                dx = ix - sourceX;
                                distance = (int)MathF.Sqrt(dx * dx + dy);
                                if (distance < min) min = distance;
                            }
                        }
                        if (-min < min_distance) min_distance = -min;
                        targetAssist[y, x].Distance = -min;
                    }
                }
            }
            float clampDist = max_distance - min_distance;
            for (x = 0; x < targetWidth; x++)
            {
                for (y = 0; y < targetHeight; y++)
                {
                    targetAssist[y, x].Distance -= (int)min_distance;
                    float value = targetAssist[y, x].Distance / clampDist;
                    output[y, x] = (byte)(value * 255);
                }
            }
        }

        /// <summary>
        /// 生成符号距离场
        /// </summary>
        /// <param name="grid"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void GenerateSDF(ref SDFGrid grid)
        {
            if (grid.Distance == null) return;
            int height = grid.Distance.GetLength(0);
            int width = grid.Distance.GetLength(1);
            for(int y = 0; y < height; y++)
            {
                for(int x = 0; x < width; x++)
                {
                    Point p = grid.GetDistance(x, y);
                    grid.Compare(ref p, x, y, -1, 0);
                    grid.Compare(ref p, x, y, 0, -1);
                    grid.Compare(ref p, x, y, -1, -1);
                    grid.Compare(ref p, x, y, 1, -1);
                    grid.SetDistance(x, y, p);
                }
                for(int x = width -1; x >= 0; x--)
                {
                    Point p = grid.GetDistance(x, y);
                    grid.Compare(ref p, x, y, 1, 0);
                    grid.SetDistance(x, y, p);
                }
            }
            for(int y = height -1; y >= 0; y--)
            {
                for(int x = width-1;x >= 0; x--)
                {
                    Point p = grid.GetDistance(x, y);
                    grid.Compare(ref p, x, y, 1, 0);
                    grid.Compare(ref p, x, y, 0, 1);
                    grid.Compare(ref p, x, y, -1, 1);
                    grid.Compare(ref p, x, y, 1, 1);
                    grid.SetDistance(x, y, p);
                }
                for(int x = 0; x < width; x++)
                {
                    Point p = grid.GetDistance(x, y);
                    grid.Compare(ref p, x, y, -1, 0);
                    grid.SetDistance(x, y, p);
                }
            }
        }
        /// <summary>
        /// 绘制二次贝塞尔曲线段
        /// </summary>
        /// <param name="bitmap"></param>
        /// <param name="x0"></param>
        /// <param name="y0"></param>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        /// <param name="x2"></param>
        /// <param name="y2"></param>
        /// <param name="color"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void PlotQuadBezierSeg(byte[,] bitmap, int x0, int y0, int x1, int y1, int x2, int y2, byte color)
        {
            /* plot a limited quadratic Bezier segment */
            int sx = x2 - x1, sy = y2 - y1;
            long xx = x0 - x1, yy = y0 - y1, xy; /* relative values for checks */
            double dx, dy, err, cur = xx * sy - yy * sx; /* curvature */
            /* sign of gradient must not change */
            if (sx * (long)sx + sy * (long)sy > xx * xx + yy * yy)
            { /* begin with longer part */
                x2 = x0; x0 = sx + x1; y2 = y0; y0 = sy + y1; cur = -cur; /* swap P0 P2 */
            }
            if (cur != 0)
            { /* no straight line */
                xx += sx; xx *= sx = x0 < x2 ? 1 : -1; /* x step direction */
                yy += sy; yy *= sy = y0 < y2 ? 1 : -1; /* y step direction */
                xy = 2 * xx * yy; xx *= xx; yy *= yy; /* differences 2nd degree */
                if (cur * sx * sy < 0)
                { /* negated curvature? */
                    xx = -xx; yy = -yy; xy = -xy; cur = -cur;
                }
                dx = 4.0 * sy * cur * (x1 - x0) + xx - xy; /* differences 1st degree */
                dy = 4.0 * sx * cur * (y0 - y1) + yy - xy;
                xx += xx; yy += yy; err = dx + dy + xy; /* error 1st step */
                do
                {
                    bitmap[y0, x0] = color; /* plot curve */
                    if (x0 == x2 && y0 == y2)
                    {
                        return; /* last pixel -> curve finished */
                    }
                    bool test = 2 * err < dx; /* save value for test of y step */
                    if (2 * err > dy) { x0 += sx; dx -= xy; err += dy += yy; } /* x step */
                    if (test) { y0 += sy; dy -= xy; err += dx += xx; } /* y step */
                } while (dy < 0 && dx > 0); /* gradient negates -> algorithm fails */
            }
            PlotLine(bitmap, x0, y0, x2, y2, color);
        }
       
    }
}
